/*--------------------------------------------------------------------------
    This source file is part of Abraxas Game Engine
    For the latest info, see http://code.google.com/abraxasge/
    Copyright (C) 2009, Andrew LeRoy <aleroy@member.igda.org>
    ---

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA  02110-1301, USA.
--------------------------------------------------------------------------*/
#ifndef _Abx_System_hpp_
#define _Abx_System_hpp_

#include "AbxPrerequisites.hpp"
#include "AbxOptional.hpp"
#include "AbxLogManager.hpp"
#include "AbxString.hpp"
#include "AbxException.hpp"
#include <typeinfo>

/// @defgroup SystemGroup System

/// @defgroup FactoryPolicies Factory policies
/// @ingroup SystemGroup
/// Policies specifying a factory's behavior

/// @defgroup InstancePolicies Instance policies
/// @ingroup SystemGroup
/// Policies specifying how instances are stored

/// @defgroup EventPolicies Event policies
/// @ingroup SystemGroup
/// Policies specifying how system instances communicate with one another

namespace Abx
{
    //template <class T> class Optional;
    
    ////////////////////////////////////////////////////////////////////////////////
    /// @class SingleInstance
    /// @ingroup InstancePolicies
    /// Implementation of the InstancePolicies used by the System template
    /// Allows a system's factory to create & store ONLY one instance
    ////////////////////////////////////////////////////////////////////////////////
    template <class T>
    class _AbxExport OneInstance
    {
    public: // C'STOR / D'STOR
        OneInstance()
        {
            instance_ = NULL;
        }

        ~OneInstance()
        {
            instance_ = 0;
        }

    public: // FUNCTIONS
        void insert(T* instance, bool setAsCurrent=true)
        {
            instance_ = instance;
        }

        Optional<T*> erase(T* instance)
        {
            Optional<T*> optinst;
            if (instance_ && instance_->getName() == instance->getName())
            {
                optinst = instance_;
                instance_ = 0;
            }
            return optinst;
        }

        Optional<T*> get(String const& name, bool byTypeName=false) const
        {
            Optional<T*> optinst;
            if (instance_)
            {
                if (byTypeName)
                {
                    if (instance_->getTypeName() == name)
                    { optinst = instance_; }
                }
                else
                {
                    if (instance_->getName() == name)
                    { optinst = instance_; }
                }
            }
            return optinst;
        }
        
        Optional<T*> set(String const& name, bool byTypeName=false)
        {
            return get(name, byTypeName);
        }

        Optional<T*> current()
        {
            Optional<T*> optinst;
            if (instance_)
            {
                optinst = instance_;
            }
            return optinst;
        }

    private: // DATA
        T*  instance_;
    };
    
    ////////////////////////////////////////////////////////////////////////////////
    /// @class ManyInstances
    /// @ingroup InstancePolicies
    /// Implementation of the InstancePolicies used by the System template
    /// Allows a system's factory to create & store many instances
    ////////////////////////////////////////////////////////////////////////////////
    template <class T>
    class _AbxExport ManyInstances
    {
    private: // TYPES
        typedef typename std::map<String, T*> instance_map;
        typedef typename std::map<String, T*>::iterator instance_it;
        typedef typename std::map<String, T*>::const_iterator instance_cit;
        
    public: // C'STOR / D'STOR
        ManyInstances()
        { }

        ~ManyInstances()
        {
            instances_.clear();
            curr_ = instances_.end();
        }

    public: // FUNCTIONS
        void insert(T* instance, bool setAsCurrent=true)
        {
            instances_[instance->getName()] = instance;

            if (setAsCurrent)
            {
                curr_ = instances_.find(instance->getName());
            }
        }

        Optional<T*> erase(T* instance)
        {
            Optional<T*> optinst;
            instance_it it = instances_.find(instance->getName());
            if (it != instances_.end())
            {
                if (it == curr_)
                {
                    curr_ = instances_.begin();
                }
                optinst = it->second;
                instances_.erase(it);
            }
            return optinst;
        }

        Optional<T*> get(String const& name, bool byTypeName=false) const
        {
            Optional<T*> optinst;

            instance_cit cit;
            if (byTypeName)
            {
                for (cit = instances_.begin(); cit != instances_.end(); ++cit)
                {
                    if (cit->second->getTypeName() == name)
                    { optinst = cit->second; }
                }
            }
            else
            {
                cit = instances_.find(name);
                if (cit != instances_.end())
                { optinst = cit->second; }
            }
            return optinst;
        }
        
        Optional<T*> set(String const& name, bool byTypeName=false)
        {
            Optional<T*> optinst;
            instance_cit cit;
            if (byTypeName)
            {
                for (cit = instances_.begin(); cit != instances_.end(); ++cit)
                {
                    if (cit->second->getTypeName() == name)
                    {
                        curr_ = instances_.find(cit->second->getName());
                        optinst = curr_->second;
                    }
                }
            }
            else
            {
                if (instances_.find(name) != instances_.end())
                {
                    curr_ = instances_.find(name);
                    optinst = curr_->second;
                }
            }
            return optinst;
        }

        Optional<T*> current()
        {
            Optional<T*> optinst;
            if (curr_ != instances_.end())
            {
                optinst = curr_->second;
            }
            return optinst;
        }

    private: // DATA
        instance_map instances_;
        instance_it  curr_;
    };

    ////////////////////////////////////////////////////////////////////////////////
    /// @class OneFactory
    /// @ingroup FactoryPolicies
    /// Implementation of the FactoryPolicy used by System template
    /// Allows a system to register ONLY one factory
    ////////////////////////////////////////////////////////////////////////////////
    template <class T>
    class _AbxExport OneFactory
    {
    public: // TYPES
        typedef typename T::factory_type factory_type;
        typedef typename T::options_type options_type;

    public: // C'STOR / D'STOR
        OneFactory() : counter_(0)
        { }

        ~OneFactory()
        {
            options_ = 0;
            factory_ = 0;
        }
       
    public: // FUNCTIONS
        void add(factory_type* factory)
        {
            factory_ = factory;
            options_ = &factory->getOptions();
        }

        void remove(factory_type* factory)
        {
            options_ = 0;
            factory_ = 0;
        }

        Optional<factory_type*> get(String const& typeName) const
        {
            Optional<factory_type*> optfact;
            if (factory_ && factory_->getOptions().typeName == typeName)
            {
                optfact = factory_;
            }
            return optfact;
        }

        Optional<options_type const*> getOptions(String const& typeName) const
        {
            Optional<const typename T::options_type*> optoptions;
            if (typeName == options_->typeName)
            {
                optoptions = options_;
            }
            return optoptions;
        }

        Optional<T*> create(String const& typeName, String const& instanceName)
        {
            Optional<T*> optinst;
            if (factory_ && factory_->getOptions().typeName == typeName)
            {
                if (instanceName.empty())
                {
                    StringTools::StrStreamType s;
                    s << typeName << "_" << ++counter_;
                    optinst = factory_->createInstance(s.str());
                }
                else
                {
                    optinst = factory_->createInstance(instanceName);
                }
            }
            return optinst;
        }
        
    private: // DATA
        factory_type*        factory_;
        options_type const*  options_;
        unsigned long        counter_;
    };

    
    ////////////////////////////////////////////////////////////////////////////////     
    /// @class ManyFactories    
    /// @ingroup FactoryPolicies
    /// Implementation of the FactoryPolicy used by System template
    /// Allows a system to register one or more factories 
    ////////////////////////////////////////////////////////////////////////////////
    template <class T>
    class _AbxExport ManyFactories
    {
    public: // TYPES
          typedef typename T::factory_type factory_type;
          typedef typename T::options_type options_type;

    private: // TYPES
        typedef typename std::vector<options_type const*> options_list;
        typedef typename std::vector<options_type const*>::iterator options_list_iter;
        typedef typename std::vector<options_type const*>::const_iterator options_list_citer;
        typedef typename std::list<factory_type*> factory_list;
        typedef typename std::list<factory_type*>::iterator factory_list_iter;
        
    public: // C'STOR / D'STOR
        ManyFactories() : counter_(0)
        { }
        
        ~ManyFactories()
        {
            factories_.clear();
            options_.clear();
        }

    public: // FUNCTIONS
        void add(factory_type* factory)
        {
            factories_.push_back(factory);
            options_.push_back(&factory->getOptions());
        }

        void remove(factory_type* factory)
        {
            // remove associated options
            for (options_list_iter it = options_.begin(); it != options_.end(); ++it)
            {
                if (*it == &(factory->getOptions()))
                {
                    options_.erase(it);
                    //break;
                }
            }
            factories_.remove(factory);
        }

        Optional<factory_type*> get(String const& typeName) const
        {
            Optional<factory_type*> optfact;
            for (factory_list_iter it = factories_.begin(); it != factories_.end(); ++it)
            {
                if ((*it)->getOptions().typeName == typeName)
                {
                    optfact = *it;
                    break;
                }
            }
            return optfact;
        }

        Optional<options_type const*> getOptions(String const& typeName) const
        {
            Optional<const typename T::options_type*> optoptions;
            for (options_list_citer cit = options_.begin(); cit != options_.end(); ++cit)
            {
                if (typeName == (*cit)->typeName)
                {
                    optoptions = *cit;
                }
            }
            return optoptions;
        }

        Optional<T*> create(String const& typeName, String const& instanceName)
        {
            Optional<T*> optinst;
            for (factory_list_iter it = factories_.begin(); it != factories_.end(); ++it)
            {
                if ((*it)->getOptions().typeName == typeName)
                {
                    if (instanceName.empty())
                    {
                        StringTools::StrStreamType s;
                        s << typeName << "_" << ++counter_;
                        optinst = (*it)->createInstance(s.str());
                    }
                    else
                    {
                        optinst = (*it)->createInstance(instanceName);
                    }
                    break;
                }
            }
            return optinst;
        }
        
    private: // DATA
        factory_list    factories_;
        options_list    options_;
        unsigned long   counter_;
    };
    
    ////////////////////////////////////////////////////////////////////////////////     
    /// @class BasicEventAlloc
    /// @ingroup EventPolicies
    /// Stub
    ////////////////////////////////////////////////////////////////////////////////     
    class _AbxExport BasicEventAlloc { };

    
    ////////////////////////////////////////////////////////////////////////////////     
    /// @class BasicEvents
    /// @ingroup EventPolicies
    /// Stub
    ////////////////////////////////////////////////////////////////////////////////     
    template <class T, class Alloc>
    class _AbxExport BasicEvents { };
    
    
    ////////////////////////////////////////////////////////////////////////////////     
    /// @class System    
    /// @ingroup SystemGroup
    /// 
    /// 
    ////////////////////////////////////////////////////////////////////////////////
    template
    <
        class T,
        template <class> class FactoryPolicy = ManyFactories,
        template <class> class InstancePolicy = ManyInstances,
        template <class, class> class EventModel = BasicEvents,
        typename EventAllocator = BasicEventAlloc
    >
    class _AbxExport System
    {
    public:   // TYPES
        typedef T type;
        typedef typename T::factory_type factory_type;
        typedef typename T::options_type options_type;
        
        typedef FactoryPolicy<T> factory_policy;
        typedef InstancePolicy<T> instance_policy;

    public:   // C'STOR / D'STOR
        System()
        { }

        ~System()
        { }

        
    private:  // COPY C'STOR / OPERATORS
        System(System const&);
        System& operator=(System const&);

    public:   // FUNCTIONS
        void Register(factory_type* factory)
        {
            factoryPolicy_.add(factory);
            LogManager::Instance().logMessage("Registered a system factory for '"
                + factory->getOptions().typeName + "'");
        }

        void Unregister(factory_type* factory)
        {
            // make sure this is a registered factory
            String typeName = factory->getOptions().typeName;
            Optional<factory_type*> optfact = factoryPolicy_.get(typeName);
            if (optfact.invalid())
            {
                //String systemName(typeid(*this).name);
                ABX_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
                    "Could not unregister '" + typeName + "'; the system is not currently registered",
                    "System<T>::Unregister");
            }

            // destroy all instances created by this factory
            for (Optional<T*> optinst = instancePolicy_.get(typeName, true);
                    optinst.valid();
                    optinst = instancePolicy_.get(typeName, true))
            {
                optinst = instancePolicy_.erase(*optinst);
            }
            factoryPolicy_.remove(factory);
            LogManager::Instance().logMessage("Unregistered a system factory for '"
                + factory->getOptions().typeName + "'");
        }

        options_type const* getOptions(String const& typeName) const
        {
            Optional<typename T::options_type const*> optoptions = factoryPolicy_.getOptions(typeName);
            if (optoptions.invalid())
            {
                //String systemName(typeid(*this).name);
                ABX_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
                    "System options were not found or do not exist for a system of type '" + typeName + "'",
                    "System<T>::getOptions");
            }
            return *optoptions;
        }

        T* Create(String const& typeName, String const& instanceName)
        {
            Optional<T*> optinst = instancePolicy_.get(instanceName);
            if (optinst.valid())
            {
                //String systemName(typeid(*this).name);
                ABX_EXCEPT(Exception::ERR_DUPLICATE_ITEM,
                    "System of type '" + typeName + "' already has an instance called '" + instanceName + "'",
                    "System<T>::Create");
            }
            optinst.clear();

            optinst = factoryPolicy_.create(typeName, instanceName);
            if (optinst.invalid())
            {
                //String systemName(typeid(*this).name);
                ABX_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
                    "No factory found for a system of type '" + typeName + "'",
                    "System<T>::Create");
            }
            instancePolicy_.insert(*optinst);
            return *optinst;
        }

        void Destroy(T* instance)
        {
            Optional<T*> optinst = instancePolicy_.erase(instance);
            if (optinst.invalid())
            {
                //String systemName(typeid(*this).name);
                ABX_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
                    "Unable to destroy instance '" + instance->getName() + "' because an instance with that name was not found",
                    "System<T>::Destroy");
            }

            Optional<factory_type*> optfact = factoryPolicy_.get(instance->getTypeName());
            if (optfact.invalid())
            {
                //String systemName(typeid(*this).name);
                ABX_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
                    "Unable to destroy instance '" + instance->getName() + "' because a valid factory was not found",
                    "System<T>::Destroy");
            }
            (*optfact)->destroyInstance(instance);
        }

        T* getInstance(String const& name, bool byTypeName=false) const
        {
            Optional<T*> optinst = instancePolicy_.get(name, byTypeName);
            if (optinst.invalid())
            {
                if (byTypeName)
                {
                    ABX_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
                        "Instance of type '" + name + "' not found",
                        "System<T>::getInstance("+ name +", " + to_string<bool>(byTypeName) + ")");
                }
                else
                {
                    ABX_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
                        "Instance with name '" + name + "' not found",
                        "System<T>::getInstance("+ name +", " + to_string<bool>(byTypeName) + ")");
                }
            }
            return *optinst;
        }

        T* getInstance()
        {
            Optional<T*> optinst = instancePolicy_.current();
            if (optinst.invalid())
            {
                ABX_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
                           "Current instance not set or no instances exist",
                           "System<T>::getInstance()");
            }
            return *optinst;
        }

        bool Update()
        {
            return true;
        }

        bool Update(T* instance)
        {
            return true;
        }
        
    private: // DATA
        factory_policy    factoryPolicy_;
        instance_policy   instancePolicy_;
    };

} // namespace Abx

#endif // _Abx_System_hpp_
